/*!***************************************************
 * @file: comm_protocol.c
 * @brief: 通信协议的底层源文件
 * @author: ITHAO 
 * @date: 2018.10.30
 * @note: 这里是底层功能的实现，被comm_protocol_core.c调用
 ****************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/wait.h>

#include "debug.h"
#include "comm_protocol.h"

static int analy_head(uint8 data, uint8 cmd);
static void send_ackerr(int sendfd, uint8 error);
static void send_ackok(int sendfd);
static unsigned char check_lrc(const char* pData,int nL);



/**
 * @brief	初始化通信     
 * @param	void
 * @return	-COMM_GET_FD_ERR
 *          -COMM_LISTEN_ERR
 *          -COMM_BIND_ERR
 */
int sever_comm_init(void)
{
    int ret = 0;
    int serverfd;
    struct sockaddr_in server_addr;

    serverfd = socket(AF_INET, SOCK_STREAM, 0);
    if(serverfd == -1) {
        DEBUG("socket error\r\n");
        return (-COMM_GET_FD_ERR);
    }

    //socket和端口、地址进行绑定
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_addr.sin_zero), 8);
    ret = bind(serverfd, (struct sockaddr*)&server_addr, 
                         sizeof(struct sockaddr));
    if(ret == -1) {
        DEBUG("bind error\r\n");
        return (-COMM_BIND_ERR);
    }

    ret = listen(serverfd, BACKLOG);
    if(ret == -1) {
        DEBUG("listen error");
        return (-COMM_LISTEN_ERR);
    }

    return serverfd;
}

/**
 * @brief	初始化通信     
 * @param	void
 * @return	-COMM_GET_HOST_NAME_ERR     获取主机名失败
 *          -COMM_GET_FD_ERR            建立本机通信句柄失败
 *          -COMM_CONNECT_ERR           连接目标失败
 *          clientfd                    初始化成功返回通信句柄
 * @note:
 */
int client_comm_init(void)
{
    struct hostent *host;
    int clientfd;
    struct sockaddr_in server_addr;
    int ret = 0;
    int flags = 0;
    struct timeval timeout = {5, 0};

    /*解析主机ip地址*/
    host = gethostbyname(IP_ADDR);
    if(!host) {
        DEBUG("gethostbyname error\r\n");
        return (-COMM_GET_HOST_NAME_ERR);
    }

    /*建立套接字*/
    clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if(clientfd == -1) {
        DEBUG("socket error\r\n");
        return (-COMM_GET_FD_ERR);
    }

    /*进行连接*/
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr = *((struct in_addr *)host->h_addr);
    bzero(&(server_addr.sin_zero), 8);
    ret = connect(clientfd, (struct sockaddr*)&server_addr, 
                         sizeof(struct sockaddr));
    if(ret == -1) {
        DEBUG("client connect error\r\n");
        return (-COMM_CONNECT_ERR);
    }

    /*设置socket接收信息为阻塞方式，并设置超时接受*/
    /*获取文件的flags值*/
    flags = fcntl(clientfd, F_GETFL, 0);
    /*设置成阻塞模式*/
    fcntl(clientfd, F_SETFL, flags & ~O_NONBLOCK);
    /*设置超时时间*/
    setsockopt(clientfd, SOL_SOCKET, SO_RCVTIMEO, 
               (char *)&timeout, sizeof(struct timeval));

    return clientfd;

}



/**
 * @brief	接受数据，并进行底层协议分析      
 * @param	recvfd--需要进行读取的fd
 *          data--用于写入读取的数据
 *          length--数据长度   
 * @return	TRUE                            接收数据成功成功
 *          -COMM_PARAM_NULL                入口参数指针空
 *          -COMM_RECV_BYTE_ERR             接收一个字节错误
 *          -COMM_ANAYL_HEAD_FIRST_ERR      分析帧头第一位错误
 *          -COMM_ANAYL_HEAD_SECOND_ERR     分析帧头第二位错误
 *          -COMM_ANAYL_LENGTH_ERR          分析长度错误
 *          -COMM_ANAYL_LRC_ERR             分析校验位错误
 * @note:参数data接收的数据指针使用完后需要进行free操作
 */
int recv_data(int recvfd, uint8 **data, uint16 *length)
{
    uint8 recv_status = ANALY_HEAD;
    uint8 recv_buff[4] = { 0 };
    uint8 recv_tmp = 0, recv_count = 0;
    uint8 ret = 0;
    uint8 recv_crc = 0;
    uint16 recv_length = 0;

    /* 日常判断入口参数 */
    if (!data || !length) {
        DEBUG("PARAM ERROR!\r\n");
        return (-COMM_PARAM_NULL);
    }

    while (1) {
        /* 每次循环读取一个字节的数据进行判断 */
        ret = recv(recvfd, &recv_tmp, 1, 0);
        if (ret == -1) {
            DEBUG("RECV　DATA　ERROR\r\n");
            return (-COMM_RECV_BYTE_ERR);
        }
        if (recv_status == ANALY_HEAD || recv_status == ANALY_LEN)
            recv_buff[recv_count] = recv_tmp;

        recv_count++;

        /*状态机 根据recv_status的状态对接受的字节进行不同德判断*/
        switch (recv_status) {
            /*判断帧头是否正确，不正确返回对应的ACK*/
            case ANALY_HEAD:
                if (recv_count == 1) {
                    ret = analy_head(recv_tmp, recv_count);
                    if (ret != TRUE ) {
                        send_ackerr(recvfd, ACK_RECV_HEAD_FIRST_ERR);
                        DEBUG("SEND　ACK_ERROR : %02x \r\n", ACK_RECV_HEAD_FIRST_ERR);
                        return (-COMM_ANAYL_HEAD_FIRST_ERR);
                    }
                }

                if (recv_count == 2) {
                    ret = analy_head(recv_tmp, recv_count);
                    if (ret != TRUE) {
                        send_ackerr(recvfd, ACK_RECV_HEAD_SECOND_ERR);
                        DEBUG("SEND　ACK_ERROR : %02x \r\n", ACK_RECV_HEAD_SECOND_ERR);
                        return (-COMM_ANAYL_HEAD_SECOND_ERR);
                    }
                    else
                        recv_status = ANALY_LEN;
                }
                break;

            /*分析数据长度*/
            case ANALY_LEN:
                if (recv_count == 4) {
                    recv_length = BUILD_UINT16(recv_buff[3], recv_buff[2]);
                    if (recv_length > 0) {
                        *length = recv_length;
                        recv_status = ANALY_DATA;
                        *data = (uint8 *)malloc(recv_length);
                        DEBUG("recv_length = %d\r\n", recv_length);
                        recv_count = 0;
                    }
                    else {
                        send_ackerr(recvfd, ACK_RECV_LENGTH_ERR);
                        DEBUG("SEND　ACK_ERROR : %02x \r\n", ACK_RECV_LENGTH_ERR);
                        return (-COMM_ANAYL_LENGTH_ERR); 
                    }
                }
                break;

            /*接收并分析数据*/
            case ANALY_DATA:
                /*接收数据*/
                if (recv_count <= recv_length)
                    (*data)[recv_count - 1] = recv_tmp;

                /*对校验位进行校验*/
                if (recv_count == (recv_length + 1)) {
                    recv_crc = check_lrc((char *)*data, recv_length);
                    if(recv_crc == recv_tmp) {
                        recv_status = ANALY_DONE;
                        send_ackok(recvfd);
                        DEBUG("RECV　SUCCESS !\r\n");
                    }
                    else {
                        send_ackerr(recvfd, ACK_RECV_LRC_ERR);
                        DEBUG("SEND　ACK_ERROR : %02x \r\n", ACK_RECV_LRC_ERR);
                        free(*data);
                        return (-COMM_ANAYL_LRC_ERR); 
                    }
                }
                break;

            default:
                break;
        }

        /*数据已经接受完毕了，推出主循环*/
        if (recv_status == ANALY_DONE)
            break;
    }

    return SUCCESS;
}


/**
 * @brief	发送数据，然后等待确认   
 * @param	recvfd--需要进行读取的fd  
 *          data--需要写的数据
 *          length--数据的长度 
 * @return	TRUE                成功
 *          -COMM_PARAM_NULL    入口参数指针为空
 *          -COMM_SEND_ERR      发送失败
 *          recv_ack            如果不是返回ACK_OK,返回对应的ack
 */
int send_data(int sendfd, uint8 *data, uint16 length)
{
    uint8 *packed_data;
    uint8 recv_ack = 0;
    int recvfd = 0;
    int ret = 0;
    int count = 0;

    if (!data) {
        DEBUG("PARAM ERROR!\r\n");
        return (-COMM_PARAM_NULL);
    }

    recvfd = sendfd;

    /*长度为帧头加数据长度4个字节+数据+校验位*/
    packed_data = (uint8 *)malloc(length + 4 + 1);

    packed_data[0] = DATA_HEADER_FIRST;
    packed_data[1] = DATA_HEADER_SECOND;
    packed_data[2] = HI_UINT16(length);
    packed_data[3] = LO_UINT16(length);

    /*这里不能用strncpy，用为这里的数据中间包含结束符，strncpy
        会将结束符后面的数据替换成结束符*/
    for (count = 0; count < length; count++)
        packed_data[4 + count] = data[count];

    /*计算出校验位*/
    packed_data[length + 4] = check_lrc((char *)data, length);

    send(sendfd, packed_data, length + 4 + 1, 0);
    if (ret == -1) {
            DEBUG("SEND　DATA　ERROR\r\n");
            return (-COMM_SEND_ERR);
    }

    ret = recv(recvfd, &recv_ack, 1, 0);
    if (ret == -1) {
            DEBUG("RECV　ACK　ERROR\r\n");
            perror("error is ");
            return (-COMM_RECV_ACK_ERR);
    }

    DEBUG("ack is : %02x\r\n", recv_ack);

    free(packed_data);

    if (recv_ack == ACK_OK) {
        DEBUG("SEND DATA SUCCESS !\r\n");
        return TRUE;
    }
    else {
        DEBUG("SEND DATA FAIL !\r\n");
        return recv_ack;
    }
}



/**
 * @brief	根据顶层协议分析数据
 *          将数据拆分，分别存放到结构体里面
 *          注意：后面的数据段都必须以\0结束   
 * @param	p_data_packet--用来存放分析后的数据
 *          data--被解析的数据
 *          length--数据长度 
 * @return	SUCCESS                成功
 *          -COMM_PARAM_NULL    入口参数指针为空
 *          -COMM_DATA_FIELD_LENGTH_ERR 数据段长度错误
 */
int analy_data(struct data_packet *p_data_packet, 
                uint8 *data, uint16 length) 
{
    int length_temp = 0;
    int length_sum = 0;
    int count = 0;

    if (!p_data_packet || !data) {
        DEBUG("PARAM ERROR!\r\n");
        return (-COMM_PARAM_NULL);
    }

    /* 如果本来就有数据就要先发数据free了，不然你懂的 */
    for (count = 0; count < DATA_MAX_NUM; count++) {
        if (p_data_packet->data[count]) {
            free(p_data_packet->data[count]);
            p_data_packet->data[count] = NULL;
        }
    }
    count = 0;

    p_data_packet->cmd_type = data[0];
    p_data_packet->cmd = data[1];
    p_data_packet->ack = data[2];

    while (length_sum < length - 3) {

        /* 这里加1是应为通过strlen得到的长度不包含\0 */
        length_temp = strlen((char *)&data[3 + length_sum]) + 1;
        if (length_temp > length) {
            DEBUG("ANALY DATA ERROR : TOO LONG");
            return (-COMM_DATA_FIELD_LENGTH_ERR);
        }

        p_data_packet->data[count] = (uint8 *)malloc(length_temp);
        strncpy((char *)p_data_packet->data[count],
                (char *)&data[3 + length_sum], length_temp);

        count++;
        length_sum += length_temp;
    }

    return SUCCESS;
}


/**
 * @brief	打包数据
 *          将data_packet里面的数据打包在一起 
 *          注意，后面数据段之间必须以\0隔开  
 * @param	p_data_packet--需要进行打包的数据  
 *          data--获取数据的指针变量
 *          data需要后期free
 *          length--获取长度的指针变量 
 * @return	SUCCESS                成功
 *          -COMM_PARAM_NULL    入口参数指针为空
 */
int package_data(struct data_packet *p_data_packet, 
                uint8 **data, uint16 *length) 
{
    int count = 0;
    int length_sum = 0;
    int length_temp = 0;

    if (!p_data_packet || !data || !length) {
        DEBUG("PARAM ERROR!\r\n");
        return (-COMM_PARAM_NULL);
    }

    for (count = 0; count < DATA_MAX_NUM; count++) {
        if (p_data_packet->data[count])
        /* 这里加1是算上\0 */
            length_sum += (strlen((char *)p_data_packet->data[count]) + 1);
    }
    count = 0;

    /* 算上命令类型、命令、和结果位*/
    length_sum += 3;
    *length = length_sum;
    *data = (uint8 *)malloc(length_sum);

    (*data)[0] = p_data_packet->cmd_type;
    (*data)[1] = p_data_packet->cmd;
    (*data)[2] = p_data_packet->ack;

    length_sum = 0;
    for (count = 0; count < DATA_MAX_NUM; count++) {
        if (p_data_packet->data[count]) {

            length_temp = strlen((char *)p_data_packet->data[count]) + 1;
            strncpy((char *)&(*data)[3 + length_sum],
                (char *)p_data_packet->data[count], length_temp);
            //(*data)[3 + length_temp] = '\0';

            length_sum += length_temp;
        }
    }

    return SUCCESS;
}



/**
 * @brief	分析数据的帧头是否正确   
 * @param	data--被分析的数据
 *          cmd--分析命令  
 *          1——分析帧头第一个字节
 *          2——分析枕头第二个字节  
 * @return	TRUE——成功
 *          FAIL——失败
 */
int analy_head(uint8 data, uint8 cmd)
{
    if (cmd == 1) {
        if (data == DATA_HEADER_FIRST)
            return TRUE;
        else
            return FAIL;
    }

    if (cmd == 2) {
        if (data == DATA_HEADER_SECOND)
            return TRUE;
        else
            return FAIL;
    }

    return FAIL;
}

/**
 * @brief	发送ACK_OK  
 * @param	sendfd--发送对象
 * @return	void
 */
void send_ackok(int sendfd)
{
    int ret = 0;
    uint8 data;

    data = ACK_OK;
    DEBUG("SEND　ACK_OK\r\n");
    ret = send(sendfd, &data, 1, 0);
    if (ret == -1)
         DEBUG("SEND　ACK_OK　FAIL\r\n");
}


/**
 * @brief	发送ACK错误   
 * @param	sendfd--发送对象
 *          error--被发送的ACK错误码  
 * @return	void
 */
void send_ackerr(int sendfd, uint8 error)
{
    int ret = 0;

    ret = send(sendfd, &error, 1, 0);
    if (ret == -1)
         DEBUG("SEND　ACK_ERR　FAIL\r\n");
}

/**
 * @brief	LRC校验   
 * @param	pData--校验对象
 *          nL--数据长度  
 * @return	-COMM_PARAM_NULL        入口参数为空
 */
static unsigned char check_lrc(const char* pdata,int nl)
{
    int i;
    uint8 chlrc=0;

    if (!pdata) {
        DEBUG("PARAM ERROR!\r\n");
        return (-COMM_PARAM_NULL);
    }
    
    for (i = 0 ; i < nl; i++)
    {
       chlrc ^= pdata[i];
    }
    return chlrc;
}